﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NUnit.Framework;

namespace MarsRover
{
    [TestFixture]
    class Tests
    {
        [Test]
        public void InitializeMarsControlFailureTest()
        {
            MarsControl mc = new MarsControl();
            Assert.Throws<ArgumentNullException>(() => { mc.CreateSurface(null); });
            Assert.Throws<ArgumentException>(() => { mc.CreateSurface(string.Empty); });
            Assert.Throws<ArgumentException>(() => { mc.CreateSurface("12"); });
            Assert.Throws<ArgumentException>(() => { mc.CreateSurface("123"); });
            Assert.Throws<ArgumentException>(() => { mc.CreateSurface("123"); });
            Assert.Throws<ArgumentException>(() => { mc.CreateSurface("1 2 3"); });
            Assert.Throws<ArgumentException>(() => { mc.CreateSurface("a b"); });
            Assert.Throws<ArgumentException>(() => { mc.CreateSurface("* &"); });
            Assert.Throws<ArgumentOutOfRangeException>(() => { mc.CreateSurface("-1 -1"); });
            Assert.Throws<ArgumentOutOfRangeException>(() => { mc.CreateSurface("0 0"); });
        }

        [Test]
        public void InitializeMarsControlSuccessTest()
        {
            MarsControl mc = new MarsControl();
            ISurface surface = mc.CreateSurface("5 5");
            Assert.AreEqual(5, surface.Width);
            Assert.AreEqual(5, surface.Height);
        }

        [Test]
        public void InitializeRoverFailureTest()
        {
            MarsControl mc = new MarsControl();
            ISurface surface = mc.CreateSurface("5 5");
            Assert.Throws<ArgumentNullException>(() => mc.AddRover(null, string.Empty));
            Assert.Throws<ArgumentNullException>(() => mc.AddRover(surface, null));
            Assert.Throws<ArgumentException>(() => mc.AddRover(surface, "12a"));
            Assert.Throws<ArgumentException>(() => mc.AddRover(surface, "12 a"));
            Assert.Throws<ArgumentException>(() => mc.AddRover(surface, "1 2a"));
            Assert.Throws<ArgumentException>(() => mc.AddRover(surface, "1 2"));
            Assert.Throws<ArgumentException>(() => mc.AddRover(surface, "1 2 N a"));
            Assert.Throws<ArgumentException>(() => mc.AddRover(surface, "1 1 A"));
            Assert.Throws<ArgumentException>(() => mc.AddRover(surface, "a b N"));
            Assert.Throws<ArgumentOutOfRangeException>(() => mc.AddRover(surface, "-1 1 N"));
            Assert.Throws<ArgumentOutOfRangeException>(() => mc.AddRover(surface, "-1 0 N"));
            Assert.Throws<ArgumentOutOfRangeException>(() => mc.AddRover(surface, "1 -1 N"));
            Assert.Throws<ArgumentOutOfRangeException>(() => mc.AddRover(surface, "0 -1 N"));
            Assert.Throws<ArgumentOutOfRangeException>(() => mc.AddRover(surface, "-1 -1 N"));
            Assert.Throws<ArgumentOutOfRangeException>(() => mc.AddRover(surface, "5 0 N"));
            Assert.Throws<ArgumentOutOfRangeException>(() => mc.AddRover(surface, "0 5 N"));
            Assert.Throws<ArgumentOutOfRangeException>(() => mc.AddRover(surface, "5 5 N"));
        }

        [Test]
        public void InitializeRoverSuccessTest()
        {
            MarsControl mc = new MarsControl();
            ISurface surface = mc.CreateSurface("5 5");
            var roverId = mc.AddRover(surface, "1 2 S");
            var result = mc.GetRoverCoordinates(surface, roverId);
            Assert.AreEqual("1 2 S", result); 
        }

        [Test]
        public void GetRoverFailureTest()
        {
            MarsControl mc = new MarsControl();
            ISurface surface = mc.CreateSurface("5 5");
            var roverId = mc.AddRover(surface, "1 2 S");
            Assert.Throws<ArgumentOutOfRangeException>(() => mc.GetRoverCoordinates(surface, -1));
            Assert.Throws<ArgumentOutOfRangeException>(() => mc.GetRoverCoordinates(surface, 1));
        }

        [Test]
        public void GetRoverSuccessTest()
        {
            MarsControl mc = new MarsControl();
            ISurface surface = mc.CreateSurface("5 5");
            var roverId = mc.AddRover(surface, "1 2 S");
            var result = mc.GetRoverCoordinates(surface, roverId);
            Assert.AreEqual("1 2 S", result);
        }

        [Test]
        public void RoverRotationFailureTest()
        {
            MarsControl mc = new MarsControl();
            ISurface surface = mc.CreateSurface("5 5");
            var roverId = mc.AddRover(surface, "0 0 N");
            IRover rover = surface.GetRover(roverId);
            Assert.Throws<ArgumentNullException>(() => { mc.ExecuteCommand(null, roverId, "L"); });
            Assert.Throws<ArgumentOutOfRangeException>(() => { mc.ExecuteCommand(surface, -1, "L"); });
            Assert.Throws<ArgumentOutOfRangeException>(() => { mc.ExecuteCommand(surface, 1, "L"); });
            Assert.Throws<ArgumentNullException>(() => { mc.ExecuteCommand(surface, roverId, null); });
            Assert.Throws<ArgumentOutOfRangeException>(() => { mc.ExecuteCommand(surface, roverId, string.Empty); });
            Assert.Throws<ArgumentOutOfRangeException>(() => { mc.ExecuteCommand(surface, roverId, "1"); });
            Assert.Throws<ArgumentOutOfRangeException>(() => { mc.ExecuteCommand(surface, roverId, "a"); });
            Assert.Throws<ArgumentOutOfRangeException>(() => { mc.ExecuteCommand(surface, roverId, "&"); });
            Assert.Throws<ArgumentOutOfRangeException>(() => { mc.ExecuteCommand(surface, roverId, "KLL"); });
            Assert.Throws<ArgumentOutOfRangeException>(() => { mc.ExecuteCommand(surface, roverId, "LLK"); });
            Assert.Throws<ArgumentOutOfRangeException>(() => { mc.ExecuteCommand(surface, roverId, "LL1"); });
        }

        [Test]
        public void RoverRotationSuccessTest()
        {
            MarsControl mc = new MarsControl();
            ISurface surface = mc.CreateSurface("5 5");
            var rover1Id = mc.AddRover(surface, "0 0 N");
            var rover2Id = mc.AddRover(surface, "4 4 S");
            IRover rover1 = surface.GetRover(rover1Id);
            IRover rover2 = surface.GetRover(rover2Id);

            var result = mc.GetRoverCoordinates(surface, rover1Id);
            Assert.AreEqual("0 0 N", result);
            result = mc.ExecuteCommand(surface, rover1Id, "L");
            Assert.AreEqual("0 0 W", result);
            result = mc.ExecuteCommand(surface, rover1Id, "L");
            Assert.AreEqual("0 0 S", result);
            result = mc.ExecuteCommand(surface, rover1Id, "L");
            Assert.AreEqual("0 0 E", result);
            result = mc.ExecuteCommand(surface, rover1Id, "L");
            Assert.AreEqual("0 0 N", result);

            result = mc.ExecuteCommand(surface, rover1Id, "R");
            Assert.AreEqual("0 0 E", result);
            result = mc.ExecuteCommand(surface, rover1Id, "R");
            Assert.AreEqual("0 0 S", result);
            result = mc.ExecuteCommand(surface, rover1Id, "R");
            Assert.AreEqual("0 0 W", result);
            result = mc.ExecuteCommand(surface, rover1Id, "R");
            Assert.AreEqual("0 0 N", result);

            result = mc.GetRoverCoordinates(surface, rover2Id);
            Assert.AreEqual("4 4 S", result);
            result = mc.ExecuteCommand(surface, rover2Id, "RRRR");
            Assert.AreEqual("4 4 S", result);

            result = mc.ExecuteCommand(surface, rover2Id, "LLLL");
            Assert.AreEqual("4 4 S", result);
        }

        [Test]
        public void RoverMovementTest()
        {
            MarsControl mc = new MarsControl();
            ISurface surface = mc.CreateSurface("5 5");
            mc.AddRover(surface, "2 2 N");
            mc.AddRover(surface, "2 2 S");
            mc.AddRover(surface, "2 2 E");
            mc.AddRover(surface, "2 2 W");
            
            var roverId = 0;
            var result = mc.GetRoverCoordinates(surface, roverId);
            Assert.AreEqual("2 2 N", result);
            result = mc.ExecuteCommand(surface, roverId, "MM");
            Assert.AreEqual("2 4 N", result);
            Assert.Throws<RoverException>(() => mc.ExecuteCommand(surface, roverId, "M"));

            roverId = 1;
            result = mc.GetRoverCoordinates(surface, roverId);
            Assert.AreEqual("2 2 S", result);
            result = mc.ExecuteCommand(surface, roverId, "MM");
            Assert.AreEqual("2 0 S", result);
            Assert.Throws<RoverException>(() => mc.ExecuteCommand(surface, roverId, "M"));

            roverId = 2;
            result = mc.GetRoverCoordinates(surface, roverId);
            Assert.AreEqual("2 2 E", result);
            result = mc.ExecuteCommand(surface, roverId, "MM");
            Assert.AreEqual("4 2 E", result);
            Assert.Throws<RoverException>(() => mc.ExecuteCommand(surface, roverId, "M"));
            
            roverId = 3;
            result = mc.GetRoverCoordinates(surface, roverId);
            Assert.AreEqual("2 2 W", result);
            result = mc.ExecuteCommand(surface, roverId, "MM");
            Assert.AreEqual("0 2 W", result);
            Assert.Throws<RoverException>(() => mc.ExecuteCommand(surface, roverId, "M"));
        }
    }
}
